<?php
/**
 * Controller
 * @filesource
 */
/**
 * Namespace
 */
namespace framework\engine;
/**
 * Dependencies
 */
use \framework\Front;
use \framework\library\Language;
/**
 * Base controller provides convenience features for use by other controllers
 * that extend this controller.
 * 
 * @author Randy Casburn <randy@plurious.com>
 * @copyright Copyright Plurious Software, all rights reserved
 * @license MIT
 * @license http://www.opensource.org/licenses/mit-license.html
 * @link http://plurious.com/framework/documentation/classes/Front.html
 * @version 2.1
 * 
 * @uses User
 * @uses Config
 * @uses Registry
 * @uses Action
 * @uses Document
 * @uses Url
 * @uses Request
 */
class Controller extends Language {

    /** Holds page data that will be used by the view templates */
    protected $data = array();

    /** Makes the User object available to the page controllers */
    protected $user;

    /** Makes the Config object available to the page controllers */
    protected $config;

    /** Makes the Registry object available to the page controllers */
    protected $registry;

    /** Makes the Actions object available to the page controllers */
    protected $action;

    /** Makes the Docment object available to the page controllers */
    protected $document;

    /** Makes the URL object available to the page controllers */
    protected $url;

    /** Makes the Request object available to the page controllers */
    protected $request;

    /** Holds the Cache object for use within this controller */
    private $cache;

    /**
     * Sets up primary objects as a convenience for the page controllers
     * that extend this class
     */
    public function __construct() {
        parent::__construct();
        $this->registry = Front::getInstance();
        $this->user = $this->get('user');
        if (is_object($this->user)) {
            $this->getLanguage('auth');
        }
        $this->config = $this->get('config');
        $this->actions = $this->get('Actions');
        $this->document = $this->get('document');
        $this->url = $this->get('url');
        $this->request = $this->get('request');
        $this->session = $this->get('session');
        $this->cache = $this->get('cache');
        if (!isset($this->useCache)) {
            $this->useCache = $this->config->get('useCache');
            if (!isset($this->useCache))
                $this->useCache = true;
        }
        if (!isset($this->isCachable)) {
            $this->isCachable = $this->config->get('isCachableDefault');
        }
    }

    /**
     * Convenience method for the use by controllers
     * 
     * @uses Registry
     * @see Registry::get()
     * @param string $key retrieve the associated value
     * @return mixed value associated with the key
     */
    public function get($key) {
        return $this->registry->get($key);
    }

    /**
     * Convenience method for the use by controllers
     * 
     * @uses Registry
     * @see Registry::getElement()
     * @param string $key The key pointing to an array to search
     * @param string $value The key of the array to search
     * @return mixed value associated with the [key][value] multi-dimensional array
     */
    public function getElement($key, $value) {
        return $this->registry->getElement($key, $value);
    }

    /**
     * Convenience method for the use by controllers
     * 
     * @uses Registry
     * @see Registry::set()
     * @param string $key name to provide for value
     * @param mixed $value value to be stored in registry
     */
    public function set($key, $value) {
        $this->registry->set($key, $value);
    }

    /**
     * Convenience method for the use by controllers
     * 
     * @uses Registry
     * @see Registry::set()
     * @param type $array_name Name of the array to store the key/value pair in
     * @param type $data The data to be stored in the registry element with key of keyname
     * @param string $keyname The name to give the array element that contains the new data
     */
    public function setElement($array_name, $data, $keyname = '') {
        $this->registry->setElement($array_name, $data, $keyname);
    }

    /**
     * Internal forwarding to another main action without redirecting client
     * 
     * Replaces the requested main action with a different main action.  Usually
     * used when the main action failed or the site cannot repsond for some reason.
     * 
     * @uses Actions
     * @param string $route in the form [context/]controller[/method/arg/arg/...]
     * @return array The method parameters exposed by the actions object 
     */
    protected function forward($route) {
        return $this->actions->run($route);
    }

    /**
     * Convenience method for redirecting from within controllers
     * 
     * @uses Document
     * @param string $url
     * @param int $status 
     */
    protected function redirect($url, $status = 302) {
        $this->document->redirect($url, $status);
    }

    /**
     * Adds a child controller to the list of controllers to execute
     * 
     * @uses Actions
     * @param string $child 
     */
    protected function addChild($child) {
        $this->actions->addAction($child, 'child');
    }

    /**
     * Adds children controllers listed in the controller method being executed
     * @uses Actions
     */
    protected function addChildren() {
        if (isset($this->children) && is_array($this->children)) {
            foreach ($this->children as $child) {
                $this->actions->addAction($child, 'child');
            }
        }
    }

    /**
     * Adds modules listed in the controller method being executed
     * @uses Actions
     */
    protected function addModules() {
        if (isset($this->modules) && is_array($this->modules)) {
            foreach ($this->modules as $module) {
                $this->actions->addModule($module);
            }
        }
    }

    /**
     * Combines all child controller and module output
     * 
     * Data must be consolidated prior to loading the main action template
     * to ensure all components are collected for output.
     */
    private function consolidateData() {
        $output_data = $this->get('output_data');
//        $language_data = $this->get('text');
//        $this->data = array_merge($this->data, $output_data, $language_data);
        $this->data = array_merge($this->data, $output_data);
    }

    /**
     * Recursive function is called repeatedly to construct output.
     * 
     * First called by the main action (ie. Home controller). Each of
     * the called method's children classes are then called one-by-one and each
     * in turn calls this method.  They may have children of their own so this
     * process continues until every cascaded child controller passes through
     * here. All modules are passed through precisely in the same manner.
     * Eventually, the main action will be the very last action to
     * render and it will call the appropriate rendering function.
     * 
     * @uses Document
     * @param String $type can be one of document, json, rss2.0 or atom
     */
    protected function render($type = 'document') {
        // Identify main action
        $main_action = $this->getElement('page_actions', 'main_action');
        $executing_action = strtolower(substr(get_class($this), strrpos(get_class($this),'\\')+1));
        $isMainAction = (strtolower(substr($main_action['class'], strrpos($main_action['class'],'\\')+1)) == $executing_action);
        // is there a cached version?
        if ($this->isCached($isMainAction, $type)) {
            return;
        }
        // Recursive - Add all the children controllers and execute them
        $this->addChildren();
        // Recursive - Add all the module controllers and execute them
        $this->addModules();
        // Make sure we use the correct action array
        if ($isMainAction) {
            $action = $main_action;
        } else {
            $action = $this->getElement('page_actions', $executing_action);
        }
        // If there is a template, then parse it...
        // otherwise we've already added the data to the output stream
        // so there is nothing else to do.
        if (file_exists($action['template'])) {
            ob_start();
            if ($isMainAction) {
                // Consolidate all the output constructed so far
                $this->consolidateData();
            }
            // Extract variable into the namespace
            $count = extract($this->data);
            // Parse the template file
            require($action['template']);
            $output = ob_get_clean();
            $this->writeCache($isMainAction, $output);
            // If isMainAction then this is the final time through
            if ($isMainAction) {
                $this->document->renderFinal($output, $type);
            } else {
                // Recursive
                //Otherwise we store the Child output with the output_data 
                // and provide it a key name that matches the class name
                $this->setElement('output_data', $output, $executing_action);
                $this->setElement('child_vars', $this->data, $executing_action);
            }
        }
    }

    /**
     * Allows template swapping from inside the page Controller object
     * 
     * The only limitation is that the template being swapped out must reside
     * in the same directory as the primary template.  The primary template
     * is always named the same as the controller.
     * 
     * @param string $name The name of the new template
     */
    protected function setTemplate($name) {
        $executing_action = strtolower(basename(get_class($this)));
        $action = $this->getElement('page_actions', $executing_action);
        $template = $action['template'];
        $action['template'] = str_replace($executing_action . TEMPLATE_EXTENSION, $name . TEMPLATE_EXTENSION, $template);
        $this->setElement('page_actions', $action, $executing_action);
    }

    /**
     * Checks to see if a cached file should be returned, finds it and sets 
     * output content from the cache.
     * 
     * @see Docment->render()
     * @param boolean $isMainAction To call renderFinal() or not
     * @param string $type of document to render
     * @return boolean True if content was set from cache, false otherwise
     */
    private function isCached($isMainAction, $type) {
        if (!is_object($this->registry->get('cache'))) {
            return false;
        }
        if (!$this->isCachable || !$this->useCache) {
            return false;
        }
        $class = namespace2classname(get_class($this));
        $cache = $this->cache->get($class);
        if ($cache) {
            if ($isMainAction) {
                $this->document->renderFinal($cache, $type);
            } else {
                $this->setElement('output_data', $cache, $class);
            }
            return true;
        }
        return false;
    }

    /**
     * Checks to see if the buffer outut should be cached and writes it if so
     * 
     * @param boolean $isMainAction To call renderFinal() or not
     * @param string $contents Contents from the output buffer to be stored
     * @return null
     */
    private function writeCache($isMainAction, &$contents) {
        if (!is_object($this->registry->get('cache'))) {
            return;
        }
        if (!$this->isCachable || !$this->useCache) {
            return;
        }
        $class = namespace2classname(get_class($this));
        $cache = $this->cache->set($class, $contents);
    }

}

?>
